diff options
| author | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
| commit | d49d4119811dee9270f0858f3f03e5d8cd0d95ba (patch) | |
| tree | 42101a3ccf47f7b7290637700b90a843476e56f7 /src/routes/(app)/[username] | |
| parent | 20c0c52f8b850bfbab490735b0b5cd5d08851488 (diff) | |
| parent | 9531ccbdf01b07c392b1b5cb6d8ad4680ee29d2c (diff) | |
| download | coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.tar.gz coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.zip | |
Merge branch 'self-custody'
Diffstat (limited to 'src/routes/(app)/[username]')
49 files changed, 127 insertions, 3209 deletions
diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index a213e792..30a6e325 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -82,13 +82,12 @@ <div class="container mx-auto w-full px-4 flex flex-wrap lg:flex-nowrap"> <div class="hidden lg:block lg:w-[280px] xl:w-[360px]" /> <div - class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-5 left-20 mx-auto mb-5" + class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-2 left-20 mx-auto" > - <div - class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" - > - <div class="my-auto break-words w-full">{display || username}</div> - </div> + <button type="button" on:click={toggleDetails} class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"> + <div class="my-auto break-words">{display || username}</div> + <Icon icon="qr" style="w-8 invert my-auto" /> + </button> {#if subject.address} <div @@ -112,68 +111,51 @@ <span class="text-secondary">{$t("user.followers")}</span></a > </div> - - <button - type="button" - on:click={toggleDetails} - class="flex text-base text-secondary mx-auto" - > - <div class="mt-1"> - {$t("user.moreDetails")} - </div> - <div - class:-rotate-90={showDetails} - class:rotate-180={!showDetails} - class="my-auto" - > - <Icon icon="chevron-left" style="w-6 mr-2" /> - </div> - </button> </div> {#if showDetails} - <div class="text-xl space-y-5"> + <div class="space-y-5 pb-20"> <div> - <div class="font-bold">{$t("user.profilePage")}</div> + <div class="text-secondary">{$t("user.lightningAddress")}</div> <div class="flex gap-4"> - <div class="break-all grow text-secondary"> - {profile} + <div class="break-all grow text-xl"> + {lnaddr} </div> <div class="flex mb-auto gap-1"> - <button class="my-auto" on:click={() => copy(profile)} + <button class="my-auto" on:click={() => copy(lnaddr)} ><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button > - <a - href={`/qr/${encodeURIComponent( - `${$page.url.protocol}//${profile}` - )}`} - class="my-auto" - > + <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto"> <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> </a> </div> </div> </div> <div> - <div class="font-bold">{$t("user.lightningAddress")}</div> + <div class="text-secondary">{$t("user.url")}</div> <div class="flex gap-4"> - <div class="break-all grow text-secondary"> - {lnaddr} + <div class="break-all grow text-xl"> + {profile} </div> <div class="flex mb-auto gap-1"> - <button class="my-auto" on:click={() => copy(lnaddr)} + <button class="my-auto" on:click={() => copy(profile)} ><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button > - <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto"> + <a + href={`/qr/${encodeURIComponent( + `${$page.url.protocol}//${profile}` + )}`} + class="my-auto" + > <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> </a> </div> </div> </div> <div> - <div class="font-bold">{$t("user.nostrPubkey")}</div> + <div class="text-secondary">{$t("user.nostrPubkey")}</div> <div class="flex gap-4"> - <div class="break-all grow text-secondary"> + <div class="break-all grow text-xl"> {npub} </div> <div class="flex my-auto gap-1"> @@ -188,9 +170,9 @@ </div> {#if user.id === subject.id} <div> - <div class="font-bold">{$t("user.nwc")}</div> + <div class="text-secondary">{$t("user.nwc")}</div> <div class="flex gap-4"> - <div class="break-all grow text-secondary"> + <div class="break-all grow text-xl"> {nwc} </div> <div class="flex my-auto gap-1"> @@ -237,7 +219,7 @@ {#if user && user.username !== subject.username && subject.pubkey} <a - href={`/${user.username}/messages/${subject.username}`} + href={`/messages/${subject.username}`} class="contents" > <button diff --git a/src/routes/(app)/[username]/(profile)/+page.server.ts b/src/routes/(app)/[username]/(profile)/+page.server.ts index 59eb44d5..ed4ee58c 100644 --- a/src/routes/(app)/[username]/(profile)/+page.server.ts +++ b/src/routes/(app)/[username]/(profile)/+page.server.ts @@ -1,11 +1,15 @@ import { auth, get } from "$lib/utils"; export async function load({ cookies, depends, parent }) { + depends("app:payments"); depends("app:items"); let { subject } = await parent(); + + let accounts = await get("/accounts", auth(cookies)); + let items = await get(`/${subject.id}/items`, auth(cookies)); items.map((i) => (i.quantity = 0)); - return { items }; + return { accounts, items }; } diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 32e6807a..399a33e9 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -1,161 +1,54 @@ <script> - import { browser } from "$app/environment"; - import { goto, invalidate } from "$app/navigation"; - import { btc, f, sat, post } from "$lib/utils"; + import { btc, f, sat } from "$lib/utils"; import Icon from "$comp/Icon.svelte"; + import Account from "$comp/Account.svelte"; import Balance from "$comp/Balance.svelte"; - import DeleteItem from "$comp/DeleteItem.svelte"; + import Items from "$comp/Items.svelte"; import { t } from "$lib/translations"; - import { fade } from "svelte/transition"; - import { cubicIn } from "svelte/easing"; - import { flip } from "svelte/animate"; import { installPrompt } from "$lib/store"; - import { - dndzone, - SHADOW_ITEM_MARKER_PROPERTY_NAME, - SOURCES, - TRIGGERS, - } from "svelte-dnd-action"; - export let data; - let flipDurationMs = 200; - let dropTargetStyle = {}; - - let { subject, user, items, rates } = data; + let { accounts, subject, user, items, rates } = data; $: ({ currency } = subject); $: rate = rates[currency]; - $: refresh(data); - let refresh = (d) => ({ items, subject, user, rates } = d); - let dragDisabled = - subject?.id !== user?.id || (browser && window.innerWidth < 768); - - let deleting; $: total = items.reduce((a, b) => a + b.price * b.quantity, 0); - let del = (item) => { - deleting = item; - }; - - function handleConsider(e) { - const { - items: newItems, - info: { source, trigger }, - } = e.detail; - items = newItems; - if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED) - reset(); - } - - async function handleFinalize(e) { - const { - items: newItems, - info: { source }, - } = e.detail; - items = newItems; - if (source === SOURCES.POINTER) reset(); - - if (subject?.id === user?.id) { - await post("/api/items/sort", { items }); - invalidate("app:items"); - } - } - - function startDrag(e) { - e.preventDefault(); - dragDisabled = false; - } - - function handleKeyDown(e) { - if ((e.key === "Enter" || e.key === " ") && dragDisabled) - dragDisabled = false; - } - - $: if (total > 0 && browser) { - let payButton = document.querySelector("#payButton"); - if (payButton) payButton.addEventListener("click", checkout); - } - - let checkout = async () => { - let invoice = { - type: "lightning", - amount: btc(total, rate), - currency, - items: items.filter((i) => i.quantity > 0), - prompt: subject.prompt, - memoPrompt: subject.memoPrompt, - rate, - }; - - let { id } = await post(`/${subject.username}/invoice`, { - invoice, - user: { username: subject.username }, - }); - - if (invoice.memoPrompt) { - return goto(`/${subject.username}/invoice/${id}/memo`); - } - goto(`/${subject.username}/invoice/${id}`); - }; - let reset = () => - (dragDisabled = - user?.username !== subject?.username || window.innerWidth < 768); + let refresh = (d) => ({ accounts, items, subject, user, rates } = d); + $: refresh(data); let install = async () => { if (!$installPrompt) return; await $installPrompt.prompt(); $installPrompt = null; }; -</script> - -<svelte:window on:resize={reset} /> -{#if deleting} - <DeleteItem bind:item={deleting} /> -{/if} + if (user) user.savings = 0; +</script> -<div class="space-y-5"> +<div class="space-y-2"> {#if user?.id === subject.id} - <div class="flex justify-center lg:justify-start"> - <Balance {user} {rate} /> - </div> - - {#if !user.balance} - <div class="mb-8"> - <p class="text-secondary text-lg"> - {$t("user.welcome")} - </p> - </div> - {/if} - - <div class="flex flex-wrap gap-3 justify-center w-full text-xl"> - <a href={`/send`} class="contents grow"> - <button - type="button" - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" - > - <div class="mx-auto flex gap-2"> - <Icon icon="send" style="w-8" /> - <div class="my-auto">{$t("user.dashboard.send")}</div> - </div> - </button> - </a> + {#each accounts as account} + <Account {user} {rate} {account} /> + {/each} - <a href={`/${user.username}/receive`} class="contents"> + <div class="flex justify-center gap-2"> + <a href={`/account/savings`} class="contents"> <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full justify-center" > <div class="mx-auto flex gap-2"> - <Icon icon="receive" style="w-8" /> - <div class="my-auto">{$t("user.dashboard.receive")}</div> + <Icon icon="plus" style="w-8 mx-auto" /> + <div class="my-auto text-xl whitespace-nowrap"> + {$t("accounts.addAccount")} + </div> </div> </button> </a> {#if $installPrompt} <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden" + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white xl:hidden w-full justify-center" on:click={install} > <div class="mx-auto flex gap-2"> @@ -169,115 +62,8 @@ </div> {/if} - {#if items.length} - <h2 class="text-2xl font-bold">Select items</h2> - {/if} - <div - class="grid sm:grid-cols-2 gap-4 pb-20" - use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }} - on:consider={handleConsider} - on:finalize={handleFinalize} - > - {#each items as i (i.id)} - <button - type="button" - class="w-full text-center cursor-pointer hover:opacity-80 mb-auto" - on:click={() => i.quantity++} - > - <div class="relative w-full h-64 overflow-hidden rounded-xl"> - {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]} - <div - in:fade={{ duration: 200, easing: cubicIn }} - class="custom-shadow-item" - /> - {:else if i.image} - <img - src={`/api/public/${i.image}.webp`} - alt={i.name} - class="mx-auto object-cover h-full w-full" - /> - {:else} - <div - class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full" - alt="Banner" - /> - {/if} - {#if user?.username === subject.username} - <div class="flex gap-2 justify-center absolute top-2 right-2"> - <button type="button" on:click|stopPropagation={() => {}}> - <a href={`/${user.username}/items/${i.id}`}> - <button - class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" - > - <Icon icon="edit" style="w-8 mx-auto invert" /> - </button> - </a> - </button> - <button - class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" - on:click|stopPropagation={() => del(i)} - > - <Icon icon="trash" style="w-8 mx-auto invert" /> - </button> - <button - type="button" - on:click|stopPropagation={() => {}} - class="sm:hidden" - on:mousedown={startDrag} - on:touchstart={startDrag} - on:keydown={handleKeyDown} - > - <a href={`/${user.username}/items/${i.id}`}> - <button - class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" - > - <Icon icon="move" style="w-8 mx-auto invert" /> - </button> - </a> - </button> - </div> - {/if} - </div> - <div class="bg-white rounded-xl py-2"> - <div class="flex text-left gap-2"> - <div class="overflow-hidden space-y-1"> - <div class="text-2xl break-words">{i.name}</div> - <div class="flex gap-2"> - <div class="font-semibold text-lg"> - {f(i.price, currency)} - </div> - <div class="text-secondary my-auto"> - {sat(btc(i.price, rate))} - </div> - </div> - </div> - <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums"> - {#if i.quantity} - <button - type="button" - on:click|stopPropagation={() => - i.quantity > 0 && i.quantity--} - > - <Icon icon="minus" style="w-10 min-w-10" /> - </button> - {/if} - <div class="text-2xl my-auto text-center"> - {#if i.quantity}{i.quantity}{:else}—{/if} - </div> - {#if i.quantity} - <button - type="button" - on:click|stopPropagation={() => i.quantity++} - > - <Icon icon="plus" style="w-10 min-w-10" /> - </button> - {/if} - </div> - </div> - </div> - </button> - {/each} - </div> + <Items {items} {subject} {user} {total} {currency} {rate} /> + <div class="fixed inset-x-0 mx-auto flex bottom-16" class:static={!items.length} diff --git a/src/routes/(app)/[username]/+layout.server.ts b/src/routes/(app)/[username]/+layout.server.ts index 11e6d45c..9a520036 100644 --- a/src/routes/(app)/[username]/+layout.server.ts +++ b/src/routes/(app)/[username]/+layout.server.ts @@ -1,17 +1,24 @@ import { get } from "$lib/utils"; import { error } from "@sveltejs/kit"; -export let load = async ({ cookies, depends, params: { username } }) => { +export let load = async ({ + cookies, + depends, + params: { username }, + parent, +}) => { depends("app:user"); try { let subject = await get(`/users/${username}`); + let { user } = await parent(); + if (!user) user = subject; let expires = new Date(); expires.setSeconds(expires.getSeconds() + 380 * 24 * 60 * 60); cookies.set("username", username, { path: "/", expires }); - return { subject }; + return { subject, user }; } catch (e) { error(500, "Unable to retrieve user account data"); } diff --git a/src/routes/(app)/[username]/+layout.svelte b/src/routes/(app)/[username]/+layout.svelte index dd29e3aa..ce152831 100644 --- a/src/routes/(app)/[username]/+layout.svelte +++ b/src/routes/(app)/[username]/+layout.svelte @@ -1,57 +1,9 @@ <script> - import AppHeader from "$comp/AppHeader.svelte"; - import { onNavigate } from "$app/navigation"; export let data; - - onNavigate((navigation) => { - if (!document.startViewTransition) return; - - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; - }); - }); - }); </script> -<AppHeader {data} /> <div class="content"> <slot /> </div> -<style> - @keyframes fade-in { - from { - opacity: 0; - } - } - - @keyframes fade-out { - to { - opacity: 0; - } - } - - @keyframes slide-from-right { - from { - transform: translateX(60px); - } - } - - @keyframes slide-to-left { - to { - transform: translateX(-60px); - } - } - - :root::view-transition-old(root) { - animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; - } - - :root::view-transition-new(root) { - animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; - } -</style> +<slot name="fixed" /> diff --git a/src/routes/(app)/[username]/address/+page.server.ts b/src/routes/(app)/[username]/address/+page.server.ts deleted file mode 100644 index 2f7841ab..00000000 --- a/src/routes/(app)/[username]/address/+page.server.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { auth, post } from "$lib/utils"; -import { redirect } from "@sveltejs/kit"; - -export async function load({ cookies, parent }) { - let user = (await parent()).subject; - let invoice = { type: "bitcoin" }; - let { id } = await post(`/invoice`, { invoice, user }, auth(cookies)); - redirect(307, `/${user.username}/invoice/${id}`); -} diff --git a/src/routes/(app)/[username]/address/+page.svelte b/src/routes/(app)/[username]/address/+page.svelte deleted file mode 100644 index e69de29b..00000000 --- a/src/routes/(app)/[username]/address/+page.svelte +++ /dev/null diff --git a/src/routes/(app)/[username]/card/[serial]/+page.server.ts b/src/routes/(app)/[username]/card/[serial]/+page.server.ts deleted file mode 100644 index 945eee30..00000000 --- a/src/routes/(app)/[username]/card/[serial]/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { auth, get } from "$lib/utils"; -import { redirect } from "@sveltejs/kit"; - -export let load = async ({ cookies, params }) => { - let { serial } = params; - let fund; - try { - fund = await get(`/fund/${serial}`, auth(cookies)); - } catch (e) { - console.log(e); - } - - if (fund) redirect(307, `/fund/${serial}`); - - return params; -}; diff --git a/src/routes/(app)/[username]/card/[serial]/+page.svelte b/src/routes/(app)/[username]/card/[serial]/+page.svelte deleted file mode 100644 index 1d11ebee..00000000 --- a/src/routes/(app)/[username]/card/[serial]/+page.svelte +++ /dev/null @@ -1,36 +0,0 @@ -<script> - import Card from "$comp/Card.svelte"; - import Icon from "$comp/Icon.svelte"; - export let data; - let { serial } = data; - let balance = 0; -</script> - -<div class="space-y-5"> - <div class="relative flex justify-center gap-2"> - <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {serial}</h1> - </div> - <div class="flex justify-center"> - <a href={`/send/pot/${serial}`}> - <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white justify-center gap-2" - > - <svg - xmlns="http://www.w3.org/2000/svg" - class="invert text-black peer-placeholder-shown:text-gray-300 h-6 w-6 my-auto" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" - /> - </svg> - <div class="my-auto">Add Funds</div> - </button> - </a> - </div> -</div> diff --git a/src/routes/(app)/[username]/customers/+page.svelte b/src/routes/(app)/[username]/customers/+page.svelte deleted file mode 100644 index e69de29b..00000000 --- a/src/routes/(app)/[username]/customers/+page.svelte +++ /dev/null diff --git a/src/routes/(app)/[username]/funds/+page.server.ts b/src/routes/(app)/[username]/funds/+page.server.ts deleted file mode 100644 index e69de29b..00000000 --- a/src/routes/(app)/[username]/funds/+page.server.ts +++ /dev/null diff --git a/src/routes/(app)/[username]/funds/+page.svelte b/src/routes/(app)/[username]/funds/+page.svelte deleted file mode 100644 index 25686e86..00000000 --- a/src/routes/(app)/[username]/funds/+page.svelte +++ /dev/null @@ -1,52 +0,0 @@ -<script> - import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; - import { t } from "$lib/translations"; - - export let data; - - let { funds } = data; -</script> - -<div class="mt-24 mb-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - Funds - </h1> - - <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> - <div class="text-base"> - <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> - <div class="whitespace-nowrap my-auto"> - <div class="text-secondary" /> - </div> - - <div class="flex my-auto"> - <div class="text-secondary flex"> - <div class="my-auto mr-1"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> - - <div class="my-auto">Pot</div> - </div> - </div> - - <div class="text-secondary text-right text-sm my-auto"> - <div /> - </div> - </div> - </div> - - <button - class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" - > - <Icon icon="plus" style="opacity-50 mr-2 my-auto" /> - <div class="my-auto">{$t("payments.create")}</div> - </button> - </div> -</div> - -<style> - .active * { - @apply bg-black text-white border-black; - } -</style> diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte deleted file mode 100644 index 767b7545..00000000 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ /dev/null @@ -1,60 +0,0 @@ -<script> - import { fail, post, wait } from "$lib/utils"; - import { onMount } from "svelte"; - import { goto, invalidate } from "$app/navigation"; - import Pin from "$comp/Pin.svelte"; - import Spinner from "$comp/Spinner.svelte"; - import { loginRedirect, pin, password } from "$lib/store"; - import { generate } from "$lib/nostr"; - import { browser } from "$app/environment"; - - export let data; - let { user } = data; - - let loaded; - let gen = async () => { - if (!browser) return; - - if (user.pubkey) { - goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); - return; - } - - try { - await generate(user); - } catch (e) { - console.log("error generating", e); - } - - try { - user.pin = $pin; - await post(`/${user.username}/generate`, user); - goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); - } catch (e) { - $pin = ""; - if (e.message?.startsWith("Pin")) { - fail("Wrong pin, try again"); - } else { - fail("Failed to generate keys"); - goto("/"); - } - } - }; - - onMount(() => browser && setTimeout(() => (loaded = true) && gen(), 50)); - - $: $pin?.length === 6 && gen(user); -</script> - -{#if loaded && user?.haspin && $pin?.length !== 6} - <Pin /> -{/if} - -<div class="container px-4 text-center mx-auto"> - <h1 class="text-3xl md:text-4xl font-bold mb-6"> - Generating new account keys - </h1> - <div class="flex w-full py-5 max-w-[200px] mx-auto bg-black rounded-2xl"> - <Spinner /> - </div> -</div> diff --git a/src/routes/(app)/[username]/generate/+server.ts b/src/routes/(app)/[username]/generate/+server.ts deleted file mode 100644 index 5a442982..00000000 --- a/src/routes/(app)/[username]/generate/+server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { auth, post } from "$lib/utils"; -import { error, json } from "@sveltejs/kit"; - -export async function POST({ cookies, request }) { - try { - let body = await request.json(); - return json(await post(`/user`, body, auth(cookies))); - } catch (e: any) { - console.log(e); - error(500, e.message); - } -} diff --git a/src/routes/(app)/[username]/invoice/+server.ts b/src/routes/(app)/[username]/invoice/+server.ts deleted file mode 100644 index 5e3d0300..00000000 --- a/src/routes/(app)/[username]/invoice/+server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { auth, post } from "$lib/utils"; -import { json } from "@sveltejs/kit"; - -export let POST = async ({ cookies: c, request: r }) => { - let j = await r.json(); - return json(await post("/invoice", j, auth(c))); -}; diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts b/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts deleted file mode 100644 index 41554c9f..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts +++ /dev/null @@ -1,39 +0,0 @@ -import Qr from "qrcode-base64"; -import { get } from "$lib/utils"; -import { redirect } from "@sveltejs/kit"; - -export async function load({ depends, params, url, parent }) { - depends("app:invoice"); - - let { subject, user } = await parent(); - let { id } = params; - let invoice = await get(`/invoice/${id}`); - let options = !!url.searchParams.get("options"); - - if ( - user && - invoice.uid !== user.id && - !(url.pathname.includes("tip") || url.pathname.includes("memo")) && - !options - ) { - redirect(307, `/send/invoice/${id}`); - } - - let { amount, pending, received } = invoice; - amount = parseInt(amount); - - let paid = - (!amount && (pending || received)) || - (amount > 0 && (pending >= amount || received >= amount)); - if (paid && !url.pathname.endsWith("paid")) { - redirect( - 307, - `/${params.username}/invoice/${id}/paid` + - (options ? "?options=true" : "") - ); - } - - let src = Qr.drawImg(invoice.text || "", { size: 340 }); - - return { id, invoice, subject, src }; -} diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.svelte b/src/routes/(app)/[username]/invoice/[id]/+layout.svelte deleted file mode 100644 index 4fa864ce..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/+layout.svelte +++ /dev/null @@ -1 +0,0 @@ -<slot /> diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.server.ts b/src/routes/(app)/[username]/invoice/[id]/+page.server.ts deleted file mode 100644 index a57efa64..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/+page.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -import invoice from "$lib/invoice"; - -export const actions = { - default: invoice, -}; diff --git a/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts b/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts deleted file mode 100644 index 58d56018..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts +++ /dev/null @@ -1,13 +0,0 @@ -import invoice from "$lib/invoice"; -import { redirect } from "@sveltejs/kit"; - -export async function load({ parent, params }) { - let { invoice } = await parent(); - if (!invoice.amount) { - redirect(307, `/${params.username}/invoice/${invoice.id}`); - } -} - -export const actions = { - default: invoice, -}; diff --git a/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte deleted file mode 100644 index 4d23ff19..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte +++ /dev/null @@ -1,95 +0,0 @@ -<script> - import { onMount } from "svelte"; - import { browser } from "$app/environment"; - import { invoice as inv, request } from "$lib/store"; - import { - copy, - select, - f, - sat, - get, - reverseFormat, - s, - sats, - } from "$lib/utils"; - import Icon from "$comp/Icon.svelte"; - import { t } from "$lib/translations"; - import { enhance } from "$app/forms"; - - let submitting; - let submit; - - export let data; - $: refresh(data); - let { invoice, id, user, rates } = data; - let { - amount, - hash, - items, - memoPrompt, - type, - rate, - received, - prompt, - text, - tip, - user: { username, currency }, - } = invoice; - - invoice.memo = user?.username; - - let refresh = (data) => { - $inv = null; - - ({ invoice, id } = data); - ({ - amount, - hash, - items, - memoPrompt, - type, - received, - prompt, - text, - tip, - user: { username }, - } = invoice); - }; -</script> - -<form - method="POST" - use:enhance - class:invisible={submitting} - class="container px-4 max-w-lg mx-auto space-y-5" -> - <input type="hidden" name="amount" value={amount} /> - <input type="hidden" name="tip" value={tip} /> - <input type="hidden" name="username" value={username} /> - <input type="hidden" name="rate" value={invoice.rate} /> - <input type="hidden" name="memoPrompt" value={invoice.memoPrompt} /> - <input type="hidden" name="prompt" value={invoice.prompt} /> - <input type="hidden" name="type" value={type} /> - <input type="hidden" name="hash" value={hash} /> - <input type="hidden" name="items" value={JSON.stringify(items)} /> - - <h1 class="text-4xl font-semibold text-center">{$t("invoice.addMemo")}</h1> - <div class="space-y-2"> - <input - use:select - name="memo" - class="w-full p-4 border rounded-xl text-xl" - bind:value={invoice.memo} - autocapitalize="none" - /> - <div class="text-secondary">{invoice.memoPrompt}</div> - </div> - - <button - type="submit" - bind:this={submit} - class="bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold" - > - {$t("payments.next")} - </button> -</form> diff --git a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte deleted file mode 100644 index 700ac972..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte +++ /dev/null @@ -1,85 +0,0 @@ -<script> - import { scale } from "svelte/transition"; - import Icon from "$comp/Icon.svelte"; - import { toast } from "@zerodevx/svelte-toast"; - import { browser } from "$app/environment"; - import { t } from "$lib/translations"; - import { fiat, f, s, sat, sats } from "$lib/utils"; - - export let data; - let amount, currency, rate, received, pending, tip, user, id; - $: update(data); - let update = (data) => { - ({ amount, currency, rate, received, pending, tip, user, id } = - data.invoice); - }; - - toast.pop(0); -</script> - -<div class="container px-4 text-center mx-auto"> - {#if received} - <div - class="flex w-full py-5 max-w-[200px] mx-auto" - in:scale={{ start: 0.5 }} - > - <Icon icon="check" style="mx-auto" /> - </div> - - <h1 class="text-3xl md:text-4xl font-bold mb-6"> - {$t("invoice.paymentSuccessful")} - </h1> - - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(received - tip, rate), currency)} - {#if tip} - <span class="text-lg"> - + {f(fiat(tip, rate), currency)} - </span> - {/if} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(received - tip)} - - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> - {/if} - - {#if pending} - <div - class="flex w-full py-5 max-w-[200px] mx-auto" - in:scale={{ start: 0.5 }} - > - <Icon icon="orangeclock" style="mx-auto" /> - </div> - - <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment detected</h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(pending - tip, rate), currency)} - {#if tip} - + {f(fiat(tip, rate), currency)} - {/if} - </h2> - - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(pending - tip)} - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> - {/if} -</div> - -<a href={`/${user.username}/receive`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> -</a> - -<div class="fixed bottom-10 w-full text-center"> - {$t("payments.tapAnywhere")} -</div> diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts b/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts deleted file mode 100644 index b81e2364..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import invoice from "$lib/invoice"; -import { redirect } from "@sveltejs/kit"; - -export async function load({ parent, params }) { - let { invoice } = await parent(); - if (!invoice.amount) - redirect(307, `/${params.username}/invoice/${invoice.id}`); -} - -export const actions = { - default: invoice, -}; diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte deleted file mode 100644 index 35b24bf1..00000000 --- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte +++ /dev/null @@ -1,209 +0,0 @@ -<script> - import { onMount } from "svelte"; - import { browser } from "$app/environment"; - import { invoice as inv, request } from "$lib/store"; - import { copy, focus, f, sat, get, reverseFormat, s, sats } from "$lib/utils"; - import Icon from "$comp/Icon.svelte"; - import Slider from "$comp/Slider.svelte"; - import { goto } from "$app/navigation"; - import { t } from "$lib/translations"; - import { enhance } from "$app/forms"; - import { tick } from "svelte"; - - const tipAmounts = ["No", "10%", "15%", "20%"]; - let showCustomAmount; - - let customInput; - let customTipAmount; - - let apply = async () => { - if (customTipAmount > 0) { - tipPercent = (customTipAmount / amountFiat) * 100; - showCustomAmount = false; - } - }; - - let active = (amount) => - !customTipAmount && Math.round(tipPercent) === parseInt(amount.slice(0, 2)); - - const handleCustomTipAmount = (e) => { - tipPercent = 0; - customTipAmount = e.target.value; - }; - - let timeout; - const handleSlide = (e) => { - customTipAmount = ""; - if (customInput) { - customInput.value = ""; - } - tipPercent = e.target.value; - }; - - let submitting; - const handleTipButtonClick = async (amount) => { - submitting = true; - if (amount === "No") { - tipPercent = 0; - } else { - tipPercent = parseInt(amount.slice(0, 2)); - } - - await tick(); - submit.click(); - }; - - let submit; - - export let data; - $: refresh(data); - let { invoice, id, user, rates } = data; - let { - amount, - hash, - items, - type, - rate, - received, - prompt, - text, - tip, - user: { username, currency }, - } = invoice; - - let qr; - let tipPercent = 0; - - let fullscreen; - - let refresh = (data) => { - $inv = null; - - ({ invoice, id } = data); - ({ - amount, - hash, - items, - type, - received, - prompt, - text, - tip, - user: { username }, - } = invoice); - - tipPercent = (tip / amount) * 100; - }; - - $: currency = user?.currency || invoice.currency; - $: rate = invoice.rate * (rates[currency] / rates[invoice.currency]); - - $: tip = Math.round((amount / 100) * tipPercent); - - $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); - - $: tipAmount = ((tip * rate) / sats).toFixed(2); - - $: invoiceAmountFiatFormatted = f(amountFiat, currency); - - onMount(() => ($request = undefined)); -</script> - -<div class="container px-4 max-w-lg text-center mx-auto"> - <form method="POST" use:enhance class:invisible={submitting}> - <input type="hidden" name="amount" value={amount} /> - <input type="hidden" name="tip" value={tip} /> - <input type="hidden" name="username" value={username} /> - <input type="hidden" name="rate" value={invoice.rate} /> - <input type="hidden" name="prompt" value="false" /> - <input type="hidden" name="type" value={type} /> - <input type="hidden" name="hash" value={hash} /> - <input type="hidden" name="items" value={JSON.stringify(items)} /> - - {#if invoice.memoPrompt} - <input type="hidden" name="memoPrompt" value={invoice.memoPrompt} /> - {/if} - - <h1 class="text-4xl font-semibold my-8">{$t("invoice.addTipq")}</h1> - {#if !showCustomAmount} - <div class="my-2 text-xl">{Math.round(tipPercent)}%</div> - - <Slider handle={handleSlide} bind:value={tipPercent} /> - - <div class="flex mb-8 text-lg"> - <div> - <span class="mr-1"> - {f(amountFiat, currency)} - <span class="font-semibold">+{f(tipAmount, currency)}</span> - </span> - </div> - - <div class="ml-auto text-lg"> - {sat(amount)} - <span class="font-semibold">+{sat(tip)}</span> - </div> - </div> - - {#if tip} - <div> - <button type="button" on:click={() => submit.click()}>Next</button> - <button type="button" on:click={() => (tipPercent = 0)}>Reset</button> - </div> - {:else} - {#each tipAmounts as amount} - <button - type="button" - class:active={active(amount, tipPercent)} - on:click={() => handleTipButtonClick(amount)}>{amount}</button - > - {/each} - {/if} - - <div> - <button type="button" on:click={() => (showCustomAmount = true)} - >Custom</button - > - </div> - {/if} - - <button type="submit" bind:this={submit} /> - </form> - - {#if showCustomAmount} - <form on:submit|preventDefault={apply}> - <div class="relative"> - <span - class="absolute top-[18px] left-5 font-semibold {!customTipAmount - ? 'opacity-50' - : 'opacity-100'}">$</span - > - <input - bind:this={customInput} - type="number" - step="0.01" - on:input={(e) => handleCustomTipAmount(e)} - class="pl-10" - placeholder="Custom amount" - use:focus - /> - <button - type="submit" - class="w-full mt-2 bg-black text-white font-semibold py-3 px-7 rounded-2xl {!customTipAmount - ? 'opacity-50' - : 'opacity-100 hover:opacity-80'}" - disabled={!customTipAmount}>Apply</button - > - </div> - </form> - {/if} -</div> - -<style> - .active { - @apply !bg-black !text-white; - } - - .container button[type="button"] { - @apply w-full mt-2 bg-primary text-black hover:bg-black hover:text-white font-semibold py-5 px-7 rounded-2xl; - } -</style> diff --git a/src/routes/(app)/[username]/invoice/sent/+page.svelte b/src/routes/(app)/[username]/invoice/sent/+page.svelte deleted file mode 100644 index b95bc85e..00000000 --- a/src/routes/(app)/[username]/invoice/sent/+page.svelte +++ /dev/null @@ -1,11 +0,0 @@ -<script> - import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; - import { request } from "$lib/store"; -</script> - -Invoice sent to -<div class="flex"> - <Avatar user={$request.requester} size={12} /> - <h1 class="my-auto"><b>{$request.requester.username}</b> is ready to pay</h1> -</div> diff --git a/src/routes/(app)/[username]/items/[id]/+page.server.ts b/src/routes/(app)/[username]/items/[id]/+page.server.ts deleted file mode 100644 index 67cebd93..00000000 --- a/src/routes/(app)/[username]/items/[id]/+page.server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { fd, auth, get, post } from "$lib/utils"; -import { fail, redirect } from "@sveltejs/kit"; - -export let load = async ({ params: { id } }) => { - let item = await get(`/items/${id}`); - return { item }; -}; - -export const actions = { - default: async ({ cookies, request, url }) => { - let form = await fd(request); - try { - await post(`/items`, form, auth(cookies)); - } catch (e: any) { - return fail(400, { message: e.message }); - } - - let success = `/${url.pathname.split("/")[1]}`; - redirect(307, success); - }, -}; diff --git a/src/routes/(app)/[username]/items/[id]/+page.svelte b/src/routes/(app)/[username]/items/[id]/+page.svelte deleted file mode 100644 index 65720350..00000000 --- a/src/routes/(app)/[username]/items/[id]/+page.svelte +++ /dev/null @@ -1,18 +0,0 @@ -<script> - import { t } from "$lib/translations"; - import ItemForm from "$comp/ItemForm.svelte"; - import { fail } from "$lib/utils"; - - export let data; - export let form; - - $: form?.message && fail(form.message); - let { item, user } = data; -</script> - -<div class="container max-w-xl mx-auto px-4"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t("items.edit")} - </h1> - <ItemForm {item} {user} /> -</div> diff --git a/src/routes/(app)/[username]/items/create/+page.server.ts b/src/routes/(app)/[username]/items/create/+page.server.ts deleted file mode 100644 index 2e006a3e..00000000 --- a/src/routes/(app)/[username]/items/create/+page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { fail, redirect } from "@sveltejs/kit"; -import { fd, auth, post } from "$lib/utils"; - -export const actions = { - default: async ({ cookies, request, url }) => { - let form = await fd(request); - try { - await post("/items", form, auth(cookies)); - } catch (e: any) { - return fail(400, { message: e.message }); - } - - let success = `/${url.pathname.split("/")[1]}`; - redirect(307, success); - }, -}; diff --git a/src/routes/(app)/[username]/items/create/+page.svelte b/src/routes/(app)/[username]/items/create/+page.svelte deleted file mode 100644 index b82889c9..00000000 --- a/src/routes/(app)/[username]/items/create/+page.svelte +++ /dev/null @@ -1,19 +0,0 @@ -<script> - import { t } from "$lib/translations"; - import ItemForm from "$comp/ItemForm.svelte"; - import { fail } from "$lib/utils"; - - export let data; - export let form; - - $: form?.message && fail(form.message); - let { user } = data; - let item = {}; -</script> - -<div class="container max-w-xl mx-auto px-4"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t("items.add")} - </h1> - <ItemForm {item} {user} /> -</div> diff --git a/src/routes/(app)/[username]/map/+page.server.ts b/src/routes/(app)/[username]/map/+page.server.ts deleted file mode 100644 index 616d28f6..00000000 --- a/src/routes/(app)/[username]/map/+page.server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { get } from "$lib/utils"; - -// export const prerender = true; -export let load = async () => { - let data = { locations: [] }; - - try { - data = await get("/locations"); - } catch (e) {} - - return data; -}; diff --git a/src/routes/(app)/[username]/map/+page.svelte b/src/routes/(app)/[username]/map/+page.svelte deleted file mode 100644 index bfa115e4..00000000 --- a/src/routes/(app)/[username]/map/+page.svelte +++ /dev/null @@ -1,8 +0,0 @@ -<script> - import LocationsMap from "$comp/LocationsMap.svelte"; - export let data; - - let { locations } = data; -</script> - -<LocationsMap {locations} /> diff --git a/src/routes/(app)/[username]/messages/+page.server.ts b/src/routes/(app)/[username]/messages/+page.server.ts deleted file mode 100644 index 94659621..00000000 --- a/src/routes/(app)/[username]/messages/+page.server.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { get } from "$lib/utils"; -import { redirect } from "@sveltejs/kit"; - -interface Message { - created_at: number; -} - -interface Note { - created_at: number; - seen: number; -} - -export async function load({ params, parent, }) { - let { user } = await parent(); - if (user.username !== params.username) redirect(307, `/${params.username}`); - let { since = 0 } = params; - - let messages = [], - notes = []; - - if (user?.pubkey) { - let { pubkey } = user; - try { - messages = await get(`/${pubkey}/${since}/messages`); - messages = messages.sort((a: Message, b: Message) => b.created_at - a.created_at); - } catch (e) { - console.log(`failed to fetch nostr messages`, e); - } - - try { - notes = await get(`/${pubkey}/notes`); - } catch (e) { - console.log(`failed to fetch nostr notes for ${pubkey}`, e); - } - - notes.map((e: Note) => { - e.seen = e.created_at; - }); - } - - return { messages, notes }; -} diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte deleted file mode 100644 index 4738ea2c..00000000 --- a/src/routes/(app)/[username]/messages/+page.svelte +++ /dev/null @@ -1,92 +0,0 @@ -<script> - import { t } from "$lib/translations"; - import Avatar from "$comp/Avatar.svelte"; - import { event as e, password } from "$lib/store"; - import { browser } from "$app/environment"; - import { decrypt } from "$lib/nostr"; - - export let data; - - let { messages, notes, invoices, sent, received, subject, user, rates } = - data; - $: refresh(data); - let refresh = (d) => - ({ messages, notes, invoices, sent, received, subject, user, rates } = d); - - let keys = new Set(); - let latest = []; - let ready; - - e.subscribe(async (event) => { - if (!(browser && event && ready)) return; - if ( - event.recipient?.id === user.id && - !~latest.findIndex((m) => m.id === event.id) - ) { - event.content = await decrypt({ event, user }); - - let i = latest.findIndex((m) => m.pubkey === event.pubkey); - let popped; - if (~i) popped = latest.splice(i, 1); - else popped = latest.pop(); - - latest.unshift(event); - latest = latest; - } - }); - - $: initialize($password); - let initialize = async (p) => { - let i = 0; - ready = false; - if (!(browser && user)) return; - while (i < messages.length) { - let event = messages[i]; - i++; - - if (!(event.author && event.recipient)) continue; - - let k = - event.author.id === user.id - ? event.recipient?.pubkey - : event.author.pubkey; - - if (!keys.has(k)) { - keys.add(k); - event.content = await decrypt({ event, user }); - if (event.content) latest.push(event); - } - } - - latest = latest; - ready = true; - }; -</script> - -<div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t("user.messages")} - </h1> - {#if latest.length && user?.id === subject.id} - <div class="relative"> - {#each latest as { content, pubkey, author, recipient }} - {@const user = author.id === user.id ? recipient : author} - <a href={`/${subject.username}/messages/${user.username}`}> - <div class="flex hover:bg-gray-100 p-4 rounded-2xl"> - <div class="my-auto"> - <Avatar {user} size={"20"} disabled={true} /> - </div> - <div class="my-auto truncate"> - <div class="my-auto ml-1 text-lg font-bold">{user.username}</div> - <div class="my-auto ml-1 text-secondary text-lg truncate"> - {content} - </div> - </div> - </div> - </a> - {/each} - </div> - {:else} - <div class="text-center">{$t("user.noMessages")}</div> - {/if} -</div> diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts b/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts deleted file mode 100644 index 6be9f6ec..00000000 --- a/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { get } from "$lib/utils"; -import type { Message } from "$lib/types"; - -export async function load({ params, parent }) { - let { user } = await parent(); - let { since = 0 } = params; - - let messages: Message[] = []; - let recipient = await get(`/users/${params.recipient}`); - - try { - messages = await get(`/${user.pubkey}/${since}/messages`); - - messages = messages - .filter( - (m: Message) => m.recipient?.id === recipient.id || m.author?.id === recipient.id - ) - .sort((a, b) => a.created_at - b.created_at); - } catch (e) { - console.log(`failed to fetch nostr messages`, e); - } - - return { messages, recipient, user }; -} diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte b/src/routes/(app)/[username]/messages/[recipient]/+page.svelte deleted file mode 100644 index 98638389..00000000 --- a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte +++ /dev/null @@ -1,191 +0,0 @@ -<script> - import { cubicInOut } from "svelte/easing"; - import { browser } from "$app/environment"; - import { t } from "$lib/translations"; - import { format } from "date-fns"; - import { scale, fade, fly } from "svelte/transition"; - import { enhance } from "$app/forms"; - import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; - import { back, fail, focus } from "$lib/utils"; - import { sign, send, encrypt, decrypt } from "$lib/nostr"; - import { event as e, password } from "$lib/store"; - import { tick, onMount } from "svelte"; - import { getEventHash } from "nostr-tools"; - - export let data; - - function fadeScale( - node, - { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 } - ) { - const o = +getComputedStyle(node).opacity; - const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/); - const s = m ? m[1] : 1; - const is = 1 - baseScale; - - return { - delay, - duration, - css: (t) => { - const eased = easing(t); - return `opacity: ${eased * o}; transform: scale(${ - eased * s * is + baseScale - })`; - }, - }; - } - - let { messages, recipient, user } = data; - let input, pane; - - $: initialize($password); - let initialize = async (p) => { - await Promise.all( - messages.map( - async (event) => (event.message = await decrypt({ event, user })) - ) - ); - - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - }; - - e.subscribe(async (event) => { - let found = ~messages.findIndex((m) => m.id === event?.id); - if (event?.recipient.id === user.id && !found) { - event.message = await decrypt({ event, user }); - messages.push(event); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - } - }); - - let sent, submitting, message; - let submit = async () => { - submitting = true; - - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 4, - tags: [["p", recipient.pubkey]], - }; - - event.message = message; - - message = ""; - - event.author = user; - event.recipient = recipient; - - messages.push(event); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - - try { - event.content = await encrypt({ - message: event.message, - recipient: recipient.pubkey, - user, - }); - event.id = getEventHash(event); - await sign({ event, user }); - await send(event); - - sent = true; - } catch (e) { - console.log(e); - fail("Failed to send message"); - } - - message = ""; - submitting = false; - }; - - let keydown = (e) => { - if (e.key === "Enter") { - e.preventDefault(); - submit(); - } - }; -</script> - -<div class="container max-w-xl mx-auto px-4 space-y-5"> - <button - type="button" - class="hover:opacity-80" - data-sveltekit-preload-data="false" - on:click={back} - > - <Icon icon="arrow-left" style="w-10" /> - </button> - - <div - class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8" - bind:this={pane} - > - {#each messages as { id, author, message, created_at, pubkey }} - {@const ours = pubkey === user.pubkey} - {@const theirs = !ours} - - {#if message} - <div - class="flex gap-2" - class:flex-row-reverse={theirs} - class:justify-end={theirs} - in:fadeScale={{ - easing: cubicInOut, - baseScale: 0.5, - }} - > - <div - class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg" - class:ours - class:theirs - :key={id} - > - {message} - </div> - <div class="mt-auto"> - <Avatar user={author} size={"12"} /> - </div> - </div> - <div class="text-sm text-gray-400 mb-6" class:text-right={ours}> - {format(new Date(created_at * 1000), "MMM d")}, - {format(new Date(created_at * 1000), "h:mm aa")} - </div> - {/if} - {/each} - </div> - - <form - method="POST" - class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2" - on:submit|preventDefault={submit} - > - <input type="hidden" name="requester_id" value={user.id} /> - <input type="hidden" name="recipient" value={recipient.username} /> - - <div - use:focus - contenteditable - class="grow break-all py-4 outline-none mt-0 pl-4" - bind:innerHTML={message} - on:keydown={keydown} - /> - <button type="submit" class="my-auto shrink-0"> - <Icon icon="send" style="w-8" /> - </button> - </form> -</div> - -<style> - .ours { - @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto; - } - - .theirs { - @apply bg-gray-100 text-black rounded-bl-none; - } -</style> diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.server.ts b/src/routes/(app)/[username]/payments/[...page]/+page.server.ts deleted file mode 100644 index 070349f8..00000000 --- a/src/routes/(app)/[username]/payments/[...page]/+page.server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { get, auth } from "$lib/utils"; - -let limit = 25; - -export async function load({ cookies, depends, params }) { - depends("app:payments"); - - let parts = params.page.split("/"); - - let start, end, page; - if (parts.length === 1) [page] = parts; - if (parts.length === 2) [start, page] = parts; - if (parts.length === 3) [start, end, page] = parts; - if (!parseInt(page)) page = 1; - - let offset = (page - 1) * limit; - if (!start) start = Date.now() / 1000 - 24 * 60 * 60; - - let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`; - if (end) url += `&end=${end * 1000}`; - - let { count, payments, incoming, outgoing } = await get(url, auth(cookies)); - let pages = new Array(Math.ceil(count / limit)); - - return { payments, page, pages, start, end, incoming, outgoing }; -} diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/payments/[...page]/+page.svelte deleted file mode 100644 index 1efdc2ec..00000000 --- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte +++ /dev/null @@ -1,315 +0,0 @@ -<script> - import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; - import { onMount } from "svelte"; - import { format } from "date-fns"; - import { newPayment } from "$lib/store"; - import { t } from "$lib/translations"; - import { get, f, s, sat, sats, types } from "$lib/utils"; - import { page } from "$app/stores"; - import { differenceInDays, getUnixTime, sub } from "date-fns"; - import { goto, invalidate } from "$app/navigation"; - - export let data; - - let { start, end, user, rates, payments } = data; - - let change = ({ target: { value } }) => goto(value); - let link = (p) => { - if (p.pot) return `/pot/${p.pot}`; - if (p.with) return "/" + p.with.username; - return `/p/${p.id}`; - }; - - $: presets = [ - { - title: $t("payments.day"), - start: sub(new Date(), { days: 1 }), - end: null, - }, - { - title: $t("payments.week"), - start: sub(new Date(), { days: 7 }), - end: null, - }, - { - title: $t("payments.month"), - start: sub(new Date(), { months: 1 }), - end: null, - }, - { - title: $t("payments.all"), - start: sub(new Date(), { years: 5 }), - end: null, - }, - ]; - - $: selection = start - ? presets.findIndex( - (p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1 - ) - : 0; - - let p, - incoming, - outgoing, - pages = []; - $: data && - ({ page: p, pages, start, end, incoming, outgoing, payments } = data); - - $: $page && ($newPayment = false); - $: $newPayment && invalidate(`/users/${user.username}`); - - $: path = $page.params.page - ? $page.url.pathname.substring(0, $page.url.pathname.lastIndexOf("/")) - : $page.url.pathname; - - let csv = async () => { - let url = `/${user.username}/payments`; - if (start) url += `/${start}`; - if (end) url += `/${end}`; - - let { payments } = await get(url); - - payments = payments.map((p) => ({ - ...p, - created: new Date(p.created), - total: p.amount + (p.tip || 0), - platform_fee: p.ourfee, - amount_fiat: f((p.amount * p.rate) / sats, p.currency), - fee_fiat: p.fee ? f((p.fee * p.rate) / sats, p.currency) : null, - platform_fee_fiat: p.ourfee ? f((p.ourfee * p.rate) / sats, p.currency) : null, - tip_fiat: p.tip ? f((p.tip * p.rate) / sats, p.currency) : null, - total_fiat: f(((p.amount + (p.tip || 0)) * p.rate) / sats, p.currency), - })); - - let keys = [ - "type", - "id", - "created", - "rate", - "currency", - "amount", - "tip", - "total", - "fee", - "platform_fee", - "amount_fiat", - "tip_fiat", - "total_fiat", - "fee_fiat", - "platform_fee_fiat", - ]; - - let csv = - keys.map((k) => `"${k}"`).join(",") + - "\n" + - payments - .map((r) => keys.map((k) => `"${r[k] || ""}"`).join(",")) - .join("\n"); - - let filename = "payments.csv"; - let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); - if (navigator.msSaveBlob) { - navigator.msSaveBlob(blob, filename); - } else { - let link = document.createElement("a"); - if (link.download !== undefined) { - let url = URL.createObjectURL(blob); - link.setAttribute("href", url); - link.setAttribute("download", filename); - link.style.visibility = "hidden"; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - } - }; -</script> - -<div class="mt-24 mb-20 payments"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t("payments.header")} - </h1> - - <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> - <div class="flex text-md text-secondary relative"> - <div class="mx-auto flex justify-center w-full gap-1"> - {#each presets as { start, end, title }, i} - <a - class:active={selection === i} - href={`/${user.username}/payments/${getUnixTime(start) + "/"}1`} - > - <button - class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]" - > - <div class="my-auto">{title}</div> - </button> - </a> - {/each} - </div> - </div> - - <div class="flex flex-wrap justify-center mb-8"> - {#if pages.length > 1} - {#each pages as _, i} - <a - class="mr-1 last:mr-0" - href={`${path}/${i + 1}`} - class:active={parseInt(p) === i + 1} - > - <div - class="border py-2 rounded-full border-2 w-12 h-12 hover:opacity-80 text-center" - > - {i + 1} - </div> - </a> - {/each} - {/if} - </div> - - <div class="text-base"> - {#each payments as p} - <a href={`/payment/${p.id}`}> - <div class="grid grid-cols-12 border-b h-24 hover:bg-gray-100 px-4"> - <div class="whitespace-nowrap my-auto col-span-3"> - <div class="font-bold" class:text-red-800={p.amount < 0}> - {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} - {#if p.tip} - <span class="text-sm text-secondary"> - +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% - </span> - {/if} - </div> - - <div class="text-secondary"> - {sat(Math.abs(p.amount) + (p.tip || 0))} - </div> - </div> - - <div - class="flex my-auto col-span-5 truncate text-ellipsis overflow-hidden mx-auto" - > - {#if p.type === types.pot} - <a href={`/pot/${p.memo}`}> - <div class="text-secondary flex"> - <div class="my-auto mr-1"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> - - <div class="my-auto">Pot</div> - </div> - </a> - {:else if p.with} - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.with} size={12} disabled={true} /> - </div> - <div class="my-auto ml-1 text-secondary"> - {p.with.username} - </div> - </div> - {:else} - <div class="text-secondary flex"> - {#if [types.ecash, types.lightning].includes(p.type)} - <div class="text-3xl">⚡️</div> - {:else if p.type === types.bitcoin} - <div class="my-auto mr-1"> - <img - src="/images/bitcoin.svg" - class="w-12 border-4 border-transparent" - alt="Bitcoin" - /> - </div> - {:else if p.type === types.liquid} - <div class="my-auto mr-1"> - <img - src="/images/liquid.svg" - class="w-12 border-4 border-transparent" - alt="Liquid" - /> - </div> - {/if} - - <div class="my-auto"> - {p.amount > 0 - ? p.confirmed - ? "Received" - : "Pending" - : "Sent"} - </div> - </div> - {/if} - </div> - - <div class="text-secondary text-right text-sm my-auto col-span-3"> - <div> - {format(new Date(p.created), "h:mm aaa")} - </div> - <div> - {format(new Date(p.created), "MMM d, yy")} - </div> - </div> - </div> - </a> - {:else} - <p class="text-secondary text-lg text-center">{$t("payments.empty")}</p> - {/each} - </div> - - <div class="grid grid-cols-3 w-full text-center text-lg"> - {#each Object.keys(incoming) as c} - <span class="text-base text-secondary text-left"></span> - <!-- <span class="text-base text-secondary" --> - <!-- >{$t("payments.subtotal")}</span --> - <!-- > --> - <span class="text-base text-secondary"> - {#if tipsIn || tipsOut}{$t("payments.tips")}{/if} - </span> - <span class="text-base text-secondary" - >{$t("payments.total")}</span - > - - {@const totalIn = incoming[c]?.fiat || 0} - {@const tipsIn = incoming[c]?.fiatTips || 0} - {@const subtotalIn = totalIn - tipsIn} - - {@const totalOut = -outgoing[c]?.fiat || 0} - {@const tipsOut = outgoing[c]?.fiatTips || 0} - {@const subtotalOut = totalOut - tipsOut} - - <span class="text-left text-base text-secondary" - >{$t("payments.income")}</span - > - <!-- <span><b>{subtotalIn ? f(subtotalIn, c) : "-"}</b></span> --> - <span><b>{tipsIn ? f(tipsIn, c) : "-"}</b></span> - <span><b>{totalIn ? f(totalIn, c) : "-"}</b></span> - - <span class="text-left text-base text-secondary" - >{$t("payments.expenditure")}</span - > - <!-- <span><b>{subtotalOut ? f(subtotalOut, c) : "-"}</b></span> --> - <span><b>{tipsOut ? f(tipsOut, c) : "-"}</b></span> - <span><b>{totalOut ? f(totalOut, c) : "-"}</b></span> - {/each} - </div> - - <button - class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" - on:click={csv} - > - <Icon icon="save" style="opacity-50 mr-2 my-auto" /> - <div class="my-auto">{$t("payments.export")}</div> - </button> - </div> -</div> - -<style> - .active * { - @apply bg-black text-white border-black; - } - - .payments { - view-transition-name: payments; - } -</style> diff --git a/src/routes/(app)/[username]/payments/[...page]/+server.ts b/src/routes/(app)/[username]/payments/[...page]/+server.ts deleted file mode 100644 index a81faaf3..00000000 --- a/src/routes/(app)/[username]/payments/[...page]/+server.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { get, auth } from "$lib/utils"; -import { json } from "@sveltejs/kit"; - -export async function GET({ cookies, params }) { - let parts = params.page.split("/"); - - let start, end; - if (parts.length === 1) [start] = parts; - if (parts.length === 2) [start, end] = parts; - - if (!start) start = Date.now() / 1000 - 24 * 60 * 60; - - let url = `/payments?start=${start * 1000}`; - if (end) url += `&end=${end * 1000}`; - - let { payments } = await get(url, auth(cookies)); - return json({ payments }); -} diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.svelte b/src/routes/(app)/[username]/receive/+page.svelte index 28bfe27b..11587211 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+page.svelte +++ b/src/routes/(app)/[username]/receive/+page.svelte @@ -1,5 +1,4 @@ <script> - import { enhance } from "$app/forms"; import { send } from "$lib/socket"; import { btc, @@ -28,7 +27,6 @@ export let data; - let ndef; let showOptions; let reading = async ({ message, serialNumber }) => { @@ -41,6 +39,7 @@ $: refresh(data); let { invoice, id, subject, user, src } = data; let { + aid, amount, hash, type, @@ -56,6 +55,7 @@ let refresh = async (data) => { ({ invoice, id, user, src } = data); ({ + aid, amount, hash, type, @@ -66,21 +66,6 @@ } = invoice); tipPercent = (tip / amount) * 100; - - // if (browser && window.NDEFReader) { - // try { - // ndef = new NDEFReader(); - // await ndef.scan(); - // - // ndef.removeEventListener('readingerror', readingerror); - // ndef.removeEventListener('reading', reading); - // - // ndef.addEventListener('readingerror', readingerror); - // ndef.addEventListener('reading', reading); - // } catch (e) { - // console.log(e); - // } - // } }; $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); @@ -96,14 +81,7 @@ send("subscribe", invoice); }); - if ($amountPrompt && !tip) toggleAmount(); - } - }); - - onDestroy(() => { - if (ndef) { - ndef.removeEventListener("readingerror", readingerror); - ndef.removeEventListener("reading", reading); + if ($amountPrompt && tip === undefined) toggleAmount(); } }); @@ -114,7 +92,7 @@ let setType = async (type) => { invoice.type = type; - ({ id } = await post(`/${username}/invoice`, { + ({ id } = await post(`/invoice`, { invoice, user: { username, currency }, })); @@ -128,7 +106,7 @@ amount = newAmount; invoice.amount = newAmount; invoice.tip = 0; - ({ id } = await post(`/${username}/invoice`, { + ({ id } = await post(`/invoice`, { invoice, user: { username, currency }, })); @@ -157,14 +135,14 @@ > <img {src} - class="mx-auto z-10 shadow-inner rounded-2xl" + class="mx-auto z-10 max-w-[360px]" bind:this={qr} alt={txt} /> </a> </div> {:else} - <div class="break-all pb-5 text-lg text-center"> + <div class="break-all pb-5 text-center text-secondary text-xl"> {txt} </div> {/if} @@ -209,7 +187,7 @@ </div> {/if} - <div class="text-secondary space-y-2 text-xl"> + <div class="text-secondary space-y-2 text-xl pt-2"> <button type="button" class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border whitespace-nowrap mb-2" @@ -257,72 +235,72 @@ </div> </div> - <div class="flex justify-around text-secondary"> - <button - class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center" - class:bg-primary={type === types.bitcoin} - class:text-black={type === types.bitcoin} - on:click={() => setType(types.bitcoin)} - > - <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> - <div class="my-auto text-lg">Bitcoin</div> - </button> + {#if aid === user.id} + <div class="grid grid-cols-2 justify-around text-secondary"> + <button + class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + class:bg-primary={type === types.bitcoin} + class:text-black={type === types.bitcoin} + on:click={() => setType(types.bitcoin)} + > + <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> + <div class="my-auto text-lg">Bitcoin</div> + </button> - <button - class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center" - class:bg-primary={type === types.liquid} - class:text-black={type === types.liquid} - on:click={() => setType(types.liquid)} - > - <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> - <div class="my-auto text-lg">Liquid</div> - </button> + <button + class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + class:bg-primary={type === types.liquid} + class:text-black={type === types.liquid} + on:click={() => setType(types.liquid)} + > + <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> + <div class="my-auto text-lg">Liquid</div> + </button> - <button - class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center" - class:bg-primary={type === types.lightning} - class:text-black={type === types.lightning} - on:click={() => setType(types.lightning)} - > - <div class="bg-white rounded-full w-8 h-8 text-center flex"> - <div class="m-auto">⚡️</div> - </div> - <div class="my-auto text-lg">Lightning</div> - </button> - </div> + <button + class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + class:bg-primary={type === types.lightning} + class:text-black={type === types.lightning} + on:click={() => setType(types.lightning)} + > + <div class="bg-white rounded-full w-8 h-8 text-center flex"> + <div class="m-auto">⚡️</div> + </div> + <div class="my-auto text-lg">Lightning</div> + </button> - {#if type === types.liquid} - <div class="flex justify-center"> - <div class="my-auto text-xl text-center text-secondary"> - {$t("payments.onlyLbtc")} - <span class="text-teal-500 font-bold">L-BTC</span>, - {$t("payments.dontDeposit")} - </div> + {#if user?.id === invoice?.user?.id} + <a href={`/receive`} class="block contents"> + <button + class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + > + <img src="/images/cash.png" class="w-8 my-auto" /> + <div class="my-auto text-lg">Ecash</div> + </button> + </a> + {/if} </div> - {/if} - {#if user?.id === invoice?.user?.id} - <a href={`/receive`} class="block"> - <button - class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border text-xl whitespace-nowrap text-secondary" - > - <Icon icon="voucher" style="w-8 my-auto" /> - <div class="my-auto"> - {$t("payments.redeemVoucher")} - </div></button - > - </a> + {#if type === types.liquid} + <div class="flex justify-center"> + <div class="my-auto text-xl text-center text-secondary"> + {$t("payments.onlyLbtc")} + <span class="text-teal-500 font-bold">L-BTC</span>, + {$t("payments.dontDeposit")} + </div> + </div> + {/if} {/if} </div> {#if settingAmount} <div - class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20" + class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50" > <div - class="relative top-1/3 mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5" + class="relative mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5 top-10" > - <form submit={setAmount}> + <form on:submit|preventDefault={setAmount}> <Numpad bind:amount={newAmount} bind:currency @@ -332,6 +310,7 @@ /> <div class="w-full flex flex-wrap gap-2"> <button + bind:this={submit} type="submit" on:click={setAmount} class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto bg-black text-white w-full" @@ -339,7 +318,6 @@ <div class="my-auto">Ok</div> </button> <button - bind:this={submit} type="button" class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto w-full" on:click={toggleAmount} @@ -350,8 +328,8 @@ </div> </form> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t("payments.amountPrompt")}</span> + <div class="flex justify-center gap-3"> + <div class="text-secondary">{$t("payments.amountPrompt")}</div> <Toggle id="notify" bind:value={$amountPrompt} /> </div> </div> diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.server.ts b/src/routes/(app)/[username]/receive/[...param]/+page.server.ts deleted file mode 100644 index 502678a9..00000000 --- a/src/routes/(app)/[username]/receive/[...param]/+page.server.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { redirect } from "@sveltejs/kit"; -import { auth, post } from "$lib/utils"; -import type { Invoice } from "$lib/types"; - -export let load = async ({ cookies, parent }) => { - let {subject, user, rates } = await parent(); - - let invoice: Invoice = { - type: "lightning", - rate: rates[user?.currency || subject?.currency], - }; - - if(!user) user = subject; - invoice = await post("/invoice", { invoice, user }, auth(cookies)); - let { id } = invoice; - - if (invoice.memoPrompt && !invoice.memo) { - redirect(307, `/${user.username}/invoice/${id}/memo`); - } else redirect(307, `/${user.username}/invoice/${id}`); -}; diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/receive/[...param]/+page.svelte deleted file mode 100644 index f9267105..00000000 --- a/src/routes/(app)/[username]/receive/[...param]/+page.svelte +++ /dev/null @@ -1,116 +0,0 @@ -<script> - import { page } from "$app/stores"; - import { fly } from "svelte/transition"; - import { enhance } from "$app/forms"; - import { tick } from "svelte"; - import { browser } from "$app/environment"; - import { t } from "$lib/translations"; - import AppHeader from "$comp/AppHeader.svelte"; - import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; - import Spinner from "$comp/Spinner.svelte"; - import { back, fail, focus } from "$lib/utils"; - - export let data; - export let form; - data.subject = data.user; - - let { contacts } = data; - let w; - - let el, text; - - let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click()); - - let paste = async () => { - text = await navigator.clipboard.readText(); - }; -</script> - -<svelte:window bind:innerWidth={w} /> - -<AppHeader {data} /> -<div class="container px-4 max-w-lg mx-auto space-y-5 mt-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t("payments.send")} - </h1> - - <form method="POST" use:enhance class="space-y-2 text-xl"> - {#if form?.error} - <div class="mb-5"> - <div class="text-red-600"> - {$t("error.unrecognizedInput")} - </div> - </div> - {/if} - - <div class="mb-2"> - <textarea - use:focus - name="text" - placeholder={$t("user.send.placeholder")} - on:keypress={keypress} - class="w-full p-4 border rounded-xl h-48 text-xl" - bind:value={text} - autocapitalize="none" - /> - </div> - - <div class="flex gap-2"> - <a href="/scan" class="w-full"> - <button - type="button" - class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center" - > - <Icon icon="camera" style="mr-2 w-6 my-auto" /> - <div class="my-auto">{$t("user.send.scan")}</div> - </button> - </a> - - <button - type="button" - class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center" - on:click={paste} - > - <Icon icon="paste" style="mr-2 w-6 my-auto" /> - <div class="my-auto">{$t("user.send.paste")}</div> - </button> - </div> - - <button - bind:this={el} - type="submit" - class="bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold" - > - {$t("user.send.next")} - </button> - </form> - - {#if contacts.length} - <div class="space-y-5"> - <h1 class="px-3 md:px-0 text-2xl font-semibold mt-10"> - {$t("user.send.contacts")} - </h1> - <div> - {#each contacts as c} - <a href={`/pay/${c.username}`}> - <div - class="border-b p-2 last:border-b-0 hover:bg-gray-100 rounded-2xl" - > - <div class="flex"> - <div> - <div class="flex"> - <Avatar user={c} size={20} disabled={true} /> - <div class="my-auto text-left"> - <p class="ml-1 text-lg break-words">{c.username}</p> - </div> - </div> - </div> - </div> - </div> - </a> - {/each} - </div> - </div> - {/if} -</div> diff --git a/src/routes/(app)/[username]/reset/+server.ts b/src/routes/(app)/[username]/reset/+server.ts deleted file mode 100644 index f7dba02c..00000000 --- a/src/routes/(app)/[username]/reset/+server.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { json } from "@sveltejs/kit"; -import { auth, post } from "$lib/utils"; - -export async function POST({ cookies, request, params }) { - let { username } = params; - let r = await request.json(); - let { password } = r; - let result = await post("/reset", { password, username }, auth(cookies)); - return json(result); -} diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts b/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts deleted file mode 100644 index 42639b38..00000000 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { fail, redirect } from "@sveltejs/kit"; -import { fd, auth, get, post } from "$lib/utils"; - -export function load({ cookies, params, url }) { - if (url.pathname.endsWith("settings")) - redirect(307, url.pathname + "/account"); - params.cookies = cookies.getAll(); - return { cookies: cookies.getAll(), tab: params.tab }; -} - -export const actions = { - default: async ({ cookies, request }) => { - let form = await fd(request); - - if (form.tab === "pos") { - form.notify = form.notify === "on"; - form.nip5 = form.nip5 === "on"; - form.prompt = form.prompt === "on"; - form.autowithdraw = form.autowithdraw === "on"; - } - - let user = { ...(await get("/me", auth(cookies))), ...form }; - - try { - ({ user } = await post(`/user`, user, auth(cookies))); - } catch (e: any) { - return fail(400, { message: e.message }); - } - - if (user.language) cookies.set("lang", user.language, { path: "/" }); - - return { user, success: true }; - }, -}; diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte deleted file mode 100644 index 12980291..00000000 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ /dev/null @@ -1,257 +0,0 @@ -<script> - import { browser } from "$app/environment"; - import { onMount, tick } from "svelte"; - import { fly } from "svelte/transition"; - import { applyAction, deserialize } from "$app/forms"; - - import Icon from "$comp/Icon.svelte"; - import Spinner from "$comp/Spinner.svelte"; - import Pin from "$comp/Pin.svelte"; - import { loading, t } from "$lib/translations"; - import { fail, auth, post, sleep, warning, success } from "$lib/utils"; - import { avatar, banner, password, pin } from "$lib/store"; - import { upload } from "$lib/upload"; - import { page } from "$app/stores"; - import { sign, send, reEncryptEntropy, setNsec } from "$lib/nostr"; - import { invalidateAll } from "$app/navigation"; - - import Account from "./_account.svelte"; - import Pos from "./_pos.svelte"; - import Security from "./_security.svelte"; - import Shopify from "./_shopify.svelte"; - - import { PUBLIC_COINOS_URL } from "$env/static/public"; - - export let data; - export let form; - - let submit; - let formElement; - - let { user, token, rates, tab, cookies } = data; - let prev; - - $: update(data); - let update = () => { - ({ user, token, rates, tab } = data); - prev = { ...user }; - }; - - $: form?.user && ({ user } = form); - - $: form?.success && throttledSuccess(); - - let justUpdated; - let throttledSuccess = () => { - if (!justUpdated) { - justUpdated = true; - success($t("user.settings.saved"), false); - setTimeout(() => (justUpdated = false), 5000); - } - }; - - $: form?.message && fail(form.message); - - $: if (form?.message?.startsWith("Pin")) { - fail("Wrong pin, try again"); - $pin = ""; - } - - let tabs = [ - { name: "account", key: "ACCOUNT", comp: Account }, - { name: "pos", key: "POINT_OF_SALE", comp: Pos }, - { name: "security", key: "SECURITY", comp: Security }, - { name: "shopify", key: "SHOPIFY", comp: Shopify }, - ]; - - $: ({ comp } = tabs.find((t) => t.name === tab)); - - let { address, id, username } = user; - let submitting; - - $: if (!$loading && $page.url.searchParams.get("verified")) - success($t("user.settings.verified")); - - async function handleSubmit() { - try { - submitting = true; - let data = new FormData(formElement); - - if (data.get("newNsec")) { - await setNsec(user, data.get("newNsec")); - data.set("nsec", user.nsec); - data.set("pubkey", user.pubkey); - } - - if (data.get("password")) { - try { - data.set( - "cipher", - await reEncryptEntropy(user, data.get("password")) - ); - } catch (e) { - console.log("Failed to encrypt keys with new password"); - throw e; - } - } - - if ($avatar) { - try { - let { hash } = JSON.parse( - await upload($avatar.file, $avatar.type, $avatar.progress, token) - ); - - data.set("profile", hash); - - await fetch(`/api/public/${hash}.webp`, { - cache: "reload", - mode: "no-cors", - }); - } catch (e) { - console.log("problem upsubmitting avatar", e); - } - } - - if ($banner) { - try { - let { hash } = JSON.parse( - await upload($banner.file, $banner.type, $banner.progress, token) - ); - - data.set("banner", hash); - - await fetch(`/api/public/${hash}.webp`, { - cache: "reload", - mode: "no-cors", - }); - } catch (e) { - console.log("problem upsubmitting banner", e); - } - } - - if (["username", "address", "profile"].some((a) => user[a] !== prev[a])) { - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 0, - content: JSON.stringify({ - name: user.username, - about: user.address, - picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp`, - }), - tags: [], - }; - - try { - await sign({ event, user }); - await send(event); - } catch (e) { - warning("Nostr profile could not be updated"); - } - } - - if (user.email !== prev.email) { - try { - cookies.get = function (n) { - return this.find((c) => c.name === n).value; - }; - - user.verified = false; - - await post( - "/api/request", - { id: user.id, email: user.email }, - auth(cookies) - ); - - warning($t("user.settings.verifying"), false); - } catch (e) { - fail(e.message); - console.log(e); - } - } - - const response = await fetch(formElement.action, { - method: "POST", - body: data, - }); - - const result = deserialize(await response.text()); - - if (result.type === "success") { - await invalidateAll(); - if (data.get("password")) $password = data.get("password"); - } - - applyAction(result); - } catch (e) { - console.log(e); - fail("Something went wrong"); - } - - submitting = false; - } -</script> - -{#if user.haspin && $pin?.length !== 6} - <Pin /> -{/if} - -<form - method="POST" - class="mb-[154px] settings" - on:submit|preventDefault={handleSubmit} - bind:this={formElement} -> - <input type="hidden" name="pin" value={$pin} /> - <input type="hidden" name="tab" value={tab} /> - - <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> - <div class="header"> - <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t("user.settings.header")} - </h1> - - <div - class="font-bold flex justify-around items-center border-b pb-3 text-secondary" - > - {#each tabs.filter((t) => t.name !== "shopify") as { name, key }} - <a href={`/${user.username}/settings/${name}`}> - <button - type="button" - class="hover:opacity-80" - class:text-black={tab === name || - (tab === "shopify" && name === "pos")} - >{$t(`user.settings.${key}`)}</button - > - </a> - {/each} - </div> - </div> - - <svelte:component this={comp} {user} {rates} {submit} /> - </div> - - <div - class="z-10 fixed bottom-0 bg-white shadow border-t w-full flex justify-center items-center py-3" - > - <button - bind:this={submit} - type="submit" - class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80" - class:bg-black={submitting} - > - {#if submitting} - <Spinner /> - {:else} - <div class="my-auto">{$t("user.settings.saveSettings")}</div> - {/if} - </button> - </div> -</form> - -<style> - .settings { - view-transition-name: settings; - } -</style> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte deleted file mode 100644 index 8e3323c7..00000000 --- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte +++ /dev/null @@ -1,190 +0,0 @@ -<script> - import { tick } from "svelte"; - import { avatar, banner } from "$lib/store"; - import Icon from "$comp/Icon.svelte"; - import { t } from "$lib/translations"; - import { page } from "$app/stores"; - - export let user, rates, submit; - - let { id } = user; - - let avatarFile, avatarInput, bannerFile, bannerInput; - - let selectAvatar = () => avatarInput.click(); - let selectBanner = () => bannerInput.click(); - - let percent; - let progress = async (event) => { - percent = Math.round((event.loaded / event.total) * 100); - }; - - let tooLarge = {}; - - let handleFile = async ({ target }, type) => { - tooLarge[type] = false; - let file = target.files[0]; - if (!file) return; - - if (file.size > 10000000) return (tooLarge[type] = true); - - if (type === "profile") { - $avatar = { id, file, type, progress }; - } else if (type === "banner") { - $banner = { id, file, type, progress }; - } - - var reader = new FileReader(); - reader.onload = async (e) => { - if (type === "profile") { - $avatar.src = e.target.result; - } else if (type === "banner") { - $banner.src = e.target.result; - } - }; - - reader.readAsDataURL(file); - }; - - if (!user.display) user.display = user.username; - - $: url = `${$page.url.host}/${user.username}`; - $: full = `${$page.url.protocol}//${url}`; - $: addr = `${user.username}@${$page.url.host}`; -</script> - -<div> - <label for="username" class="font-bold mb-1 block" - >{$t("user.settings.username")}</label - > - <div class="flex mb-2"> - <input - type="text" - name="username" - bind:value={user.username} - class="w-auto min-w-0 pr-1 grow" - /> - </div> -</div> - -<div> - <label for="display" class="font-bold mb-1 block" - >{$t("user.settings.displayName")}</label - > - <input type="text" name="display" bind:value={user.display} /> -</div> - -<div class="space-y-1 relative"> - <label for="email" class="font-bold block">{$t("user.settings.email")}</label> - <p class="text-secondary"> - {$t("user.settings.emailDescription")} - </p> - - <div class="relative"> - <input type="text" name="email" bind:value={user.email} /> - {#if user.verified} - <Icon icon="check" style="absolute right-5 top-3 w-8" /> - {:else if user.email} - <Icon icon="orangeclock" style="absolute right-5 top-3 w-8" /> - {/if} - </div> -</div> - -<div> - <span class="font-bold">{$t("user.settings.profileImage")}</span> - - <div class="flex"> - {#if $avatar || user.profile} - <div - class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer" - on:click={selectAvatar} - on:keydown={selectAvatar} - > - <img - src={$avatar?.src || `/api/public/${user.profile}.webp`} - class="absolute w-full h-full object-cover object-center visible overflow-hidden" - alt={user.username} - /> - </div> - {:else} - <div - class="rounded-full border-4 border-white p-4 bg-gradient-to-r from-primary to-gradient w-24 my-auto hover:opacity-80 cursor-pointer" - on:click={selectAvatar} - on:keydown={selectAvatar} - > - <Icon icon="logo-symbol-white" style="mx-auto" /> - </div> - {/if} - <div class="ml-2 p-2"> - <!-- found missing translation --> - <button - type="button" - class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={selectAvatar} - on:keydown={selectAvatar}>{$t("user.settings.select")}</button - > - <input - type="file" - class="hidden" - bind:this={avatarInput} - on:change={(e) => handleFile(e, "profile")} - /> - </div> - </div> - - {#if tooLarge["avatar"]} - <div class="text-red-600">Max file size 10MB</div> - {/if} -</div> - -<div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t("user.settings.bannerImage")}</span> - </div> - - {#if $banner || user.banner} - <img - src={$banner ? $banner.src : `/api/public/${user.banner}.webp`} - class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner} - alt="Banner" - /> - {:else} - <div - class="bg-gradient-to-r from-primary to-gradient w-full h-48 mb-4 cursor-pointer hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner} - alt="Banner" - /> - {/if} - - <button - type="button" - class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner}>{$t("user.settings.select")}</button - > - <input - type="file" - class="hidden" - bind:this={bannerInput} - on:change={(e) => handleFile(e, "banner")} - /> - - {#if tooLarge["banner"]} - <div class="text-red-600">Max file size 10MB</div> - {/if} -</div> - -<div> - <label for="address" class="font-bold mb-1 block" - >{$t("user.settings.about")}</label - > - <textarea - type="text" - name="address" - bind:value={user.address} - placeholder={$t("user.settings.aboutPlaceholder")} - /> -</div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte deleted file mode 100644 index 6a1c92a1..00000000 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ /dev/null @@ -1,238 +0,0 @@ -<script> - import { tick } from "svelte"; - import Icon from "$comp/Icon.svelte"; - import Numpad from "$comp/Numpad.svelte"; - import LocaleSelector from "$comp/LocaleSelector.svelte"; - import Toggle from "$comp/Toggle.svelte"; - import { locale, t } from "$lib/translations"; - import { success, fail } from "$lib/utils"; - import { page } from "$app/stores"; - - export let user, rates, submit; - - let { currency } = user; - let rate = rates[currency]; - - let fiats = Object.keys(rates).sort((a,b) => a.localeCompare(b)); - $: user.language = $locale; - let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click()); - - let editingReserve, editingThreshold, doneReserve, doneThreshold; - let doneEditing = () => { - editingReserve = false; - editingThreshold = false; - }; - - let editReserve = async () => { - editingReserve = true; - await tick(); - reserveEl.focus(); - }; - - let editThreshold = async () => { - editingThreshold = true; - await tick(); - thresholdEl.focus(); - }; - - if (!user.threshold) user.threshold = 1000000; - if (!user.reserve) user.reserve = 100000; - let reserveEl, thresholdEl; -</script> - -<div> - <label for="language" class="font-bold block mb-1" - >{$t("user.settings.locale")}</label - > - <LocaleSelector style="select-styles block py-3 w-full" /> -</div> - -<div> - <label for="currency" class="font-bold block mb-1" - >{$t("user.settings.localCurrency")}</label - > - <select - name="currency" - class="select-styles block py-3 w-full" - value={user.currency} - > - {#each fiats as fiat} - <option value={fiat}>{fiat}</option> - {/each} - </select> -</div> - -<div> - <label for="memoPrompt" class="font-bold mb-1 block" - >{$t("user.settings.memoPrompt")}</label - > - <textarea - name="memoPrompt" - placeholder={$t("user.settings.memoPromptPlaceholder")} - bind:value={user.memoPrompt} - /> -</div> - -<div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t("user.settings.notifications")}</span> - <Toggle id="notify" bind:value={user.notify} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t("user.settings.notificationsDesc")} - </p> -</div> - -<div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t("user.settings.tipPrompt")}</span> - <Toggle id="prompt" bind:value={user.prompt} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t("user.settings.tipPromptDescription")} - </p> -</div> - -<div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t("user.settings.publishNip5")}</span> - <Toggle id="nip5" bind:value={user.nip5} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t("user.settings.nip5a")} - <span class="font-bold">{user.username}@{$page.url.host}</span> - {$t("user.settings.nip5b")} - </p> -</div> - -<div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t("user.settings.autoWithdraw")}</span> - <Toggle id="autowithdraw" bind:value={user.autowithdraw} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t("user.settings.autoWithdrawDescription")} - </p> -</div> - -{#if user.autowithdraw} - <div class="mb-2"> - <label for="display" class="font-bold mb-1 block" - >{$t("user.settings.destination")}</label - > - <textarea - name="destination" - placeholder={$t("user.settings.destinationPlaceholder")} - on:keypress={keypress} - class="w-full p-4 border rounded-xl h-48" - bind:value={user.destination} - /> - </div> - - <div> - <label for="display" class="font-bold mb-1 block" - >{$t("user.settings.threshold")}</label - > - <button type="button" class="flex w-full" on:click={editThreshold}> - <div class="p-4 border rounded-2xl rounded-r-none border-r-0 bg-primary"> - ⚡️ - </div> - <div - class="border-l-0 rounded-l-none pl-2 w-full p-4 border rounded-2xl text-left" - > - {user.threshold} - </div> - <input type="hidden" name="threshold" bind:value={user.threshold} /> - </button> - <p class="text-secondary mt-1"> - {$t("user.settings.thresholdDesc")} - </p> - </div> - - <div> - <label for="display" class="font-bold mb-1 block" - >{$t("user.settings.reserve")}</label - > - <button type="button" class="flex w-full" on:click={editReserve}> - <div class="p-4 border rounded-2xl rounded-r-none border-r-0 bg-primary"> - ⚡️ - </div> - <div - class="border-l-0 rounded-l-none pl-2 w-full p-4 border rounded-2xl text-left" - > - {user.reserve} - </div> - <input type="hidden" name="reserve" bind:value={user.reserve} /> - </button> - <p class="text-secondary mt-1"> - {$t("user.settings.reserveDesc")} - </p> - </div> -{/if} - -{#if editingThreshold} - <div - class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50" - > - <div - class="relative top-40 mx-auto p-5 border shadow-lg rounded-md bg-white space-y-5 w-lg max-w-lg" - > - <h1 class="text-center text-2xl font-semibold"> - {$t("user.settings.threshold")} - </h1> - <Numpad - bind:amount={user.threshold} - bind:currency - bind:rate - bind:submit={doneReserve} - bind:element={thresholdEl} - /> - - <button - bind:this={doneReserve} - type="button" - on:click={doneEditing} - class="p-4 bg-black text-white rounded-2xl text-lg font-semibold w-full" - >Ok</button - > - </div> - </div> -{/if} - -{#if editingReserve} - <div - class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50" - > - <div - class="relative top-40 mx-auto p-5 border shadow-lg rounded-md bg-white space-y-5 w-lg max-w-lg text-center" - > - <h1 class="text-2xl font-semibold"> - {$t("user.settings.reserve")} - </h1> - <Numpad - bind:amount={user.reserve} - bind:currency - bind:rate - bind:submit={doneReserve} - bind:element={reserveEl} - /> - <button - bind:this={doneReserve} - type="button" - on:click={doneEditing} - class="p-4 bg-black text-white rounded-2xl text-lg font-semibold w-full" - >Ok</button - > - </div> - </div> -{/if} - -<a href="./shopify"> - <button - type="button" - class="flex rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full gap-2 my-5 justify-center" - > - <img src="/icons/shopify.svg" class="w-8" /> - <div class="my-auto">{$t("user.settings.shopify")}</div> - </button> -</a> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte deleted file mode 100644 index 6b44f830..00000000 --- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte +++ /dev/null @@ -1,321 +0,0 @@ -<script> - import { getMnemonic, getNsec } from "$lib/nostr"; - import { tick } from "svelte"; - import { t } from "$lib/translations"; - import Pin from "$comp/Pin.svelte"; - import Icon from "$comp/Icon.svelte"; - import Qr from "$comp/Qr.svelte"; - import { copy, post, success, fail } from "$lib/utils"; - import { pin as current } from "$lib/store"; - import { invalidate } from "$app/navigation"; - import { page } from "$app/stores"; - - export let user, rates, submit; - - let confirming2fa, - disabling2fa, - importing, - locked, - mnemonic, - newNsec, - nsec, - old, - otp, - password, - pin, - revealPassword, - revealSeed, - revealNsec, - token = "", - setting2fa, - settingPin, - verify; - - let toggleImporting = () => { - revealNsec = false; - importing = !importing; - }; - - let toggleNsec = async () => { - try { - nsec = await getNsec(user); - revealNsec = !revealNsec; - importing = false; - } catch (e) { - console.log(e); - } - }; - - let togglePassword = () => (revealPassword = !revealPassword); - let toggleSeed = async () => { - try { - mnemonic = await getMnemonic(user); - revealSeed = !revealSeed; - } catch (e) { - console.log(e); - } - }; - - $: verifying = pin?.length > 5; - $: verify && checkPin(pin); - let checkPin = async () => { - old = $current; - - try { - if (pin.length > 5 && pin === verify) { - user.haspin = true; - $current = pin; - pin = ""; - verify = ""; - submit.click(); - settingPin = false; - verifying = false; - } else { - fail("Pin mismatch, try again"); - pin = ""; - verify = ""; - verifying = false; - settingPin = true; - } - } catch (e) { - console.log(e); - fail("Problem setting PIN"); - } - }; - - let reset = () => { - token = ""; - return true; - }; - - let disablingPin = false; - - let togglePin = async () => { - if (user.haspin) { - try { - disablingPin = true; - await tick(); - submit.click(); - } catch (e) { - console.log(e); - fail("Failed to disable pin"); - } - } else { - settingPin = true; - disablingPin = false; - } - }; - - let startEnabling2fa = async () => { - reset(); - try { - if (!otp) otp = await post("/otpsecret", { pin: $current }); - } catch (e) { - console.log(e); - } - - setting2fa = true; - }; - - let startDisabling2fa = () => (disabling2fa = true); - let startConfirming2fa = () => (confirming2fa = true); - let cancel = () => { - pin = null; - token = null; - verifying = false; - settingPin = false; - setting2fa = false; - confirming2fa = false; - disabling2fa = false; - }; - - $: enable2fa(token); - let enable2fa = async (twoFa) => { - try { - if (setting2fa && token.length === 6) { - await post("/enable2fa", { token }); - success("2FA enabled"); - user.twofa = 1; - cancel(); - } - } catch (e) { - fail("Failed to enable 2FA, try again"); - } - }; - - $: disable2fa(token); - let disable2fa = async () => { - try { - if (disabling2fa && token.length === 6) { - await post("/disable2fa", { token }); - success("2FA disabled"); - delete user.twofa; - disabling2fa = false; - } - } catch (e) { - console.log(e); - fail("Failed to disable 2FA, try again"); - } - }; -</script> - -<input type="hidden" name="newpin" value={disablingPin ? "delete" : pin} /> -<input type="hidden" name="confirm" bind:value={password} /> - -<div class="relative mb-5"> - <label for="password" class="block font-bold block mb-1" - >{$t("user.settings.newPassword")}</label - > - {#if revealPassword} - <input name="password" type="text" bind:value={password} /> - {:else} - <input name="password" type="password" bind:value={password} /> - {/if} - <button - type="button" - on:click={togglePassword} - on:keydown={togglePassword} - class="absolute right-5 top-11" - > - <Icon icon={revealPassword ? "eye" : "eye-off"} /> - </button> -</div> - -<div> - <span class="font-bold mb-1" - >{verifying - ? $t("user.settings.verifyPIN") - : $t("user.settings.securityPIN")}</span - > - <p class="text-secondary mb-1"> - {$t("user.settings.securityPINDescription")} - </p> - {#if verifying} - <Pin bind:value={verify} {cancel} notify={false} /> - {:else if settingPin} - <Pin - bind:value={pin} - title={$t("user.settings.setPIN")} - {cancel} - notify={false} - /> - {:else} - <button type="button" class="primary" on:click={togglePin} - ><Icon icon="lock" style="mr-1" /> - {user.haspin - ? $t("user.settings.disablePIN") - : $t("user.settings.enablePIN")}</button - > - {/if} -</div> - -<div> - <span class="font-bold mb-1">{$t("user.settings.twofa")}</span> - <p class="text-secondary mb-4"> - {$t("user.settings.twofaDescription")} - </p> - - {#if setting2fa} - <a href={otp.uri}> - <Qr src={otp.qr} /> - </a> - - <div class="text-center my-4"> - {$t("user.settings.accountId")}<br /> - <b>{otp.secret}</b> - </div> - - <button type="button" class="primary" on:click={startConfirming2fa}> - <Icon icon="numpad" style="w-8 mr-1" /> - <div class="my-auto">Confirm</div> - </button> - {:else if user.twofa} - <button type="button" class="primary" on:click={startDisabling2fa}> - <Icon icon="mobile" style="w-8 mr-1" /> - <div class="my-auto">{$t("user.settings.twofaDisable")}</div> - </button> - {:else} - <button type="button" class="primary" on:click={startEnabling2fa}> - <Icon icon="mobile" style="w-8 mr-1" /> - <div class="my-auto">{$t("user.settings.twofaSetup")}</div> - </button> - {/if} - - {#if confirming2fa || disabling2fa} - <Pin - bind:value={token} - title="Enter 2FA Code" - {cancel} - persist={false} - notify={false} - /> - {/if} -</div> - -<!-- <div> --> -<!-- <label for="seedphrase" class="font-bold" --> -<!-- >{$t("user.settings.seedPhrase")}</label --> -<!-- > --> -<!-- --> -<!-- <p class="text-secondary mb-1"> --> -<!-- {$t("user.settings.seedDescription")} --> -<!-- </p> --> -<!-- --> -<!-- <button type="button" class="primary" on:click={toggleSeed}> --> -<!-- <Icon icon="warning" style="mr-1 w-6 my-auto" /> --> -<!-- {revealSeed ? $t("user.settings.hideSeed") : $t("user.settings.revealSeed")} --> -<!-- </button> --> -<!-- --> -<!-- {#if revealSeed} --> -<!-- <div class="text-lg"> --> -<!-- {mnemonic} --> -<!-- </div> --> -<!-- --> -<!-- <button type="button" class="primary" on:click={() => copy(mnemonic)}> --> -<!-- <Icon icon="copy" style="mr-1 w-6 my-auto" /> --> -<!-- Copy --> -<!-- </button> --> -<!-- {/if} --> -<!-- </div> --> - -<div> - <label for="seedphrase" class="font-bold" - >{$t("user.settings.nostrKeys")}</label - > - - <p class="text-secondary mb-1"> - {$t("user.settings.nostrDescription")} - </p> - - <div class="flex"> - <button type="button" class="primary" on:click={toggleNsec}> - {#if revealNsec} - <Icon icon="eye-off" style="mr-1 w-6 my-auto" /> - {$t("user.settings.hideNsec")} - {:else} - <Icon icon="warning" style="mr-1 w-6 my-auto" /> - {$t("user.settings.revealNsec")} - {/if} - </button> - - <button type="button" class="primary" on:click={toggleImporting}> - <Icon icon="save" style="mr-1 w-6 my-auto" /> - {$t("user.settings.import")} - </button> - </div> - - {#if importing} - <input name="newNsec" bind:value={newNsec} placeholder="nsec..." /> - {/if} - - {#if revealNsec} - <div class="text-lg break-all"> - {nsec} - </div> - - <button type="button" class="primary" on:click={() => copy(nsec)}> - <Icon icon="copy" style="mr-1 w-6 my-auto" /> - Copy - </button> - {/if} -</div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte deleted file mode 100644 index 0266fa9c..00000000 --- a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte +++ /dev/null @@ -1,92 +0,0 @@ -<script> - import { t } from "$lib/translations"; - import Icon from "$comp/Icon.svelte"; - import { page } from "$app/stores"; - - export let user; - $: paymentsUrl = - user.shopifyStore && - "https://admin.shopify.com/store/" + - user.shopifyStore + - "/settings/payments"; - $: checkoutUrl = - user.shopifyStore && - "https://admin.shopify.com/store/" + - user.shopifyStore + - "/settings/checkout"; - $: appsUrl = - user.shopifyStore && - "https://admin.shopify.com/store/" + - user.shopifyStore + - "/settings/apps/development"; - - let script = `<script src="${$page.url.protocol}//${$page.url.host}/${user.username}/shopify.js"><\/script>`; -</script> - -<div> - <label for="shopifyStore" class="font-bold mb-1 block">Store ID</label> - <div class="flex mb-2"> - <div - class="text-gray-600 p-4 my-auto bg-gray-100 border rounded-l-2xl border-r-0 pr-1" - > - admin.shopify.com/store/ - </div> - <input - type="text" - name="shopifyStore" - bind:value={user.shopifyStore} - class="border-l-0 rounded-l-none w-auto min-w-0 pl-1 grow" - /> - </div> - <p class="text-secondary my-2"> - Add Bitcoin under <a - class:active={!!paymentsUrl} - href={paymentsUrl || undefined}>Payments > Manual payment methods</a - > with a description like "Pay with Bitcoin". - </p> -</div> - -<div> - <label for="display" class="font-bold mb-1 block">Additional scripts</label> - <p class="text-secondary my-2"> - Copy this into <a class:active={!!paymentsUrl} href={checkoutUrl} - >Checkout -> Additional scripts</a - > - </p> - <textarea rows={5} type="text" name="shopifyScripts" readonly - >{script}</textarea - > -</div> - -<div> - <label for="shopifyToken" class="font-bold mb-1 block" - >Admin API token <span class="font-normal">(Optional)</span></label - > - <p class="text-secondary my-2"> - Coinos can automatically mark orders as Paid if you provide an API token. - <input - class="my-4" - type="text" - name="shopifyToken" - bind:value={user.shopifyToken} - /> - </p> - <p class="text-secondary my-2"> - Create a new app in <a class:active={!!appsUrl} href={appsUrl || undefined} - >Apps > App Development</a - > - </p> - - <p class="text-secondary my-2"> - Edit the Admin API scopes to enable <code class="text-black" - >write_orders</code - > permission then install the app into your store and copy the Admin API access - token here. - </p> -</div> - -<style> - .active { - @apply text-blue-500; - } -</style> diff --git a/src/routes/(app)/[username]/verify/[code]/+page.server.ts b/src/routes/(app)/[username]/verify/[code]/+page.server.ts deleted file mode 100644 index fec1a85c..00000000 --- a/src/routes/(app)/[username]/verify/[code]/+page.server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { get } from "$lib/utils"; -import { redirect } from "@sveltejs/kit"; - -export let load = async ({ params: { code } }) => { - let user; - try { - user = await get(`/verify/${code}`); - } catch (e) { - console.log(e); - return { error: "Verification failed" }; - } - - redirect(307, `/${user.username}/settings/account?verified=true`); -}; diff --git a/src/routes/(app)/[username]/verify/[code]/+page.svelte b/src/routes/(app)/[username]/verify/[code]/+page.svelte deleted file mode 100644 index 1e099dae..00000000 --- a/src/routes/(app)/[username]/verify/[code]/+page.svelte +++ /dev/null @@ -1,35 +0,0 @@ -<script> - import { scale } from "svelte/transition"; - import Icon from "$comp/Icon.svelte"; - import { t } from "$lib/translations"; - - export let data; - let { user, error } = data; -</script> - -<div class="container px-4 text-center mx-auto"> - {#if error} - <h1 class="text-3xl md:text-4xl font-bold mb-6"> - Email verification failed - </h1> - {:else if user.verified} - <div - class="flex w-full py-5 max-w-[200px] mx-auto" - in:scale={{ start: 0.5 }} - > - <Icon icon="check" style="mx-auto" /> - </div> - - <h1 class="text-3xl md:text-4xl font-bold mb-6"> - Email verified successfully! - </h1> - {/if} -</div> - -<a href={`/${user.username}`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> -</a> - -<div class="fixed bottom-10 w-full text-center"> - {$t("payments.tapAnywhere")} -</div> |
